package com.polyak.iconswitch;

import com.polyak.iconswitch.attrs.IswAttrSet;
import com.polyak.iconswitch.utils.AttrUtils;
import com.polyak.iconswitch.utils.DeviceUtils;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.element.VectorElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;

import java.math.BigDecimal;
import java.util.Locale;

/**
 * IconSwitch主要实现类
 */
public final class IconSwitch extends ComponentContainer implements
        Component.DrawTask,
        Component.EstimateSizeListener,
        Component.TouchEventListener {
    private static final int MIN_ICON_SIZE_VP = 12;
    private static final int DEFAULT_IMAGE_SIZE_VP = 18;

    private int switchWidth;
    private int switchHeight;

    private int iconSize;
    private int iconTop;
    private int iconBottom;
    private int iconOffset;

    private int thumbColorLeft;
    private int thumbColorRight;
    private int thumbDiameter;

    private PixelMapHolder leftHolder;
    private PixelMapHolder rightHolder;
    private PixelMapHolder leftHolderInactive;
    private PixelMapHolder rightHolderInactive;

    private VectorElement leftVector;
    private VectorElement rightVector;
    private VectorElement leftVectorInactive;
    private VectorElement rightVectorInactive;

    private Paint bgPaint;
    private RectFloat bgRF;
    private float bgRadius;

    private Paint thumbPaint;
    private Point thumbPoint;
    private float thumbRadius;

    private Paint iconPaint;
    private RectFloat iconLeftRF;
    private RectFloat iconRightRF;

    private boolean moving = false;
    private float touchLastX;

    private Checked currentChecked;

    private CheckedChangeListener listener;

    public IconSwitch(Context context) {
        super(context);
        init(null);
    }

    public IconSwitch(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(attrSet);
    }

    public IconSwitch(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    /**
     * 初始化操作
     *
     * @param attrs attrs
     */
    private void init(AttrSet attrs) {
        initSwitchView();
        ShapeElement shape = new ShapeElement();
        shape.setRgbColor(RgbColor.fromArgbInt(getContext().getColor(ResourceTable.Color_transparent)));
        setBackground(shape);
        iconSize = DeviceUtils.vpToPx(getContext(), DEFAULT_IMAGE_SIZE_VP);
        initAttr(attrs);
        if (leftVector != null) {
            leftVector.setAntiAlias(true);
        }
        if (rightVector != null) {
            rightVector.setAntiAlias(true);
        }
        if (leftVectorInactive != null) {
            leftVectorInactive.setAntiAlias(true);
        }
        if (rightVectorInactive != null) {
            rightVectorInactive.setAntiAlias(true);
        }
        thumbPaint.setColor(new Color(currentChecked == Checked.LEFT ? thumbColorLeft : thumbColorRight));

        calculateSwitchDimensions();
        addDrawTask(this);
        setEstimateSizeListener(this);
        setTouchEventListener(this);
    }

    private void initAttr(AttrSet attrs) {
        int colorDefBackground = getContext().getColor(ResourceTable.Color_isw_defaultBg);
        String checked = Checked.LEFT.name();
        PixelMap leftMap = null;
        PixelMap leftMapInactive = null;
        PixelMap rightMap = null;
        PixelMap rightMapInactive = null;
        if (attrs != null) {
            iconSize = AttrUtils.getInteger(attrs, IswAttrSet.ISW_ICON_SIZE, iconSize);
            leftMap = AttrUtils.getPixelMap(attrs, IswAttrSet.ISW_ICON_LEFT);
            rightMap = AttrUtils.getPixelMap(attrs, IswAttrSet.ISW_ICON_RIGHT);
            leftMapInactive = AttrUtils.getPixelMap(attrs, IswAttrSet.ISW_ICON_LEFT_INACTIVE);
            rightMapInactive = AttrUtils.getPixelMap(attrs, IswAttrSet.ISW_ICON_RIGHT_INACTIVE);
            leftVector = AttrUtils.getVector(attrs, IswAttrSet.ISW_ICON_LEFT);
            rightVector = AttrUtils.getVector(attrs, IswAttrSet.ISW_ICON_RIGHT);
            leftVectorInactive = AttrUtils.getVector(attrs, IswAttrSet.ISW_ICON_LEFT_INACTIVE);
            rightVectorInactive = AttrUtils.getVector(attrs, IswAttrSet.ISW_ICON_RIGHT_INACTIVE);
            int colorDefThumb = getContext().getColor(ResourceTable.Color_colorAccent);
            colorDefBackground = AttrUtils.getColor(attrs, IswAttrSet.ISW_BACKGROUND_COLOR, colorDefBackground);
            thumbColorLeft = AttrUtils.getColor(attrs, IswAttrSet.ISW_THUMB_COLOR_LEFT, colorDefThumb);
            thumbColorRight = AttrUtils.getColor(attrs, IswAttrSet.ISW_THUMB_COLOR_RIGHT, colorDefThumb);
            checked = AttrUtils.getString(attrs, IswAttrSet.ISW_DEFAULT_SELECTION, checked);
        }
        if (leftMap != null) {
            leftHolder = new PixelMapHolder(leftMap);
        }
        if (rightMap != null) {
            rightHolder = new PixelMapHolder(rightMap);
        }
        if (leftMapInactive != null) {
            leftHolderInactive = new PixelMapHolder(leftMapInactive);
        }
        if (rightMapInactive != null) {
            rightHolderInactive = new PixelMapHolder(rightMapInactive);
        }
        bgPaint.setColor(new Color(colorDefBackground));

        if (checked.toUpperCase(Locale.getDefault()).equals(Checked.RIGHT.name())) {
            currentChecked = Checked.RIGHT;
        } else {
            currentChecked = Checked.LEFT;
        }
    }

    private void initSwitchView() {
        bgPaint = new Paint();
        bgPaint.setAntiAlias(true);
        bgRF = new RectFloat();
        thumbPaint = new Paint();
        thumbPaint.setAntiAlias(true);
        thumbPoint = new Point();
        iconPaint = new Paint();
        iconPaint.setAntiAlias(true);
        iconLeftRF = new RectFloat();
        iconRightRF = new RectFloat();
    }

    private void calculateSwitchDimensions() {
        iconSize = Math.max(iconSize, DeviceUtils.vpToPx(getContext(), MIN_ICON_SIZE_VP));
        switchWidth = BigDecimal.valueOf(iconSize)
                .multiply(BigDecimal.valueOf(4)).intValue();
        switchHeight = Math.round(BigDecimal.valueOf(iconSize)
                .multiply(BigDecimal.valueOf(2f)).floatValue());
        iconOffset = Math.round(BigDecimal.valueOf(iconSize)
                .multiply(BigDecimal.valueOf(0.8f)).floatValue());
        iconTop = BigDecimal.valueOf(switchHeight)
                .subtract(BigDecimal.valueOf(iconSize))
                .divide(BigDecimal.valueOf(2), BigDecimal.ROUND_HALF_UP).intValue();
        iconBottom = BigDecimal.valueOf(iconTop).add(BigDecimal.valueOf(iconSize)).intValue();
        thumbDiameter = switchHeight;
    }

    @Override
    public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
        int overshootPadding = Math.round(BigDecimal.valueOf(thumbDiameter)
                .multiply(BigDecimal.valueOf(0.1f)).floatValue());
        int defW = BigDecimal.valueOf(overshootPadding)
                .multiply(BigDecimal.valueOf(2))
                .add(BigDecimal.valueOf(switchWidth)).intValue();
        int width = getSize(widthEstimateConfig, defW);
        int height = getSize(heightEstimateConfig, switchHeight);
        measureBgSize(iconSize, width, height);
        measureIconSize(width, height);
        setEstimatedSize(
                EstimateSpec.getChildSizeWithMode(width, width, EstimateSpec.NOT_EXCEED),
                EstimateSpec.getChildSizeWithMode(height, height, EstimateSpec.NOT_EXCEED)
        );
        return true;
    }

    private void measureBgSize(int imageSize, int width, int height) {
        final float centerX = BigDecimal.valueOf(width)
                .multiply(BigDecimal.valueOf(0.5f)).floatValue();
        final float centerY = BigDecimal.valueOf(height)
                .multiply(BigDecimal.valueOf(0.5f)).floatValue();
        final float halfWidth = BigDecimal.valueOf(imageSize)
                .multiply(BigDecimal.valueOf(1.75f)).floatValue();
        final float halfHeight = BigDecimal.valueOf(imageSize)
                .multiply(BigDecimal.valueOf(0.75f)).floatValue();
        bgRF.modify(
                BigDecimal.valueOf(centerX).subtract(BigDecimal.valueOf(halfWidth)).floatValue(),
                BigDecimal.valueOf(centerY).subtract(BigDecimal.valueOf(halfHeight)).floatValue(),
                BigDecimal.valueOf(centerX).add(BigDecimal.valueOf(halfWidth)).floatValue(),
                BigDecimal.valueOf(centerY).add(BigDecimal.valueOf(halfHeight)).floatValue()
        );
        bgRadius = bgRF.getHeight() * 0.5f;
    }

    private float thumbLeftMin;
    private float thumbLeftMax;

    private void measureIconSize(int width, int height) {
        iconLeftRF.modify(iconOffset, iconTop, iconOffset + iconSize, iconBottom);
        int rightIconLeft = width - iconOffset - iconSize;
        iconRightRF.modify(rightIconLeft, iconTop, rightIconLeft + iconSize, iconBottom);

        BigDecimal tempL = BigDecimal.valueOf(iconLeftRF.right)
                .subtract(BigDecimal.valueOf(iconLeftRF.left));
        BigDecimal tempR = BigDecimal.valueOf(iconRightRF.right)
                .subtract(BigDecimal.valueOf(iconRightRF.left));
        thumbLeftMin = tempL.multiply(BigDecimal.valueOf(0.5f))
                .add(BigDecimal.valueOf(iconLeftRF.left)).floatValue();
        thumbLeftMax = tempR.multiply(BigDecimal.valueOf(0.5f))
                .add(BigDecimal.valueOf(iconRightRF.left)).floatValue();
        int size = Math.min(width, height);
        thumbPoint.modify(currentChecked == Checked.LEFT ?
                        thumbLeftMin : thumbLeftMax,
                BigDecimal.valueOf(size).multiply(BigDecimal.valueOf(0.5f)).floatValue());
        thumbRadius = size * 0.5f;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        canvas.drawRoundRect(bgRF, bgRadius, bgRadius, bgPaint);
        canvas.drawCircle(thumbPoint.getPointX(), thumbPoint.getPointY(), thumbRadius, thumbPaint);
        float curThumbX = thumbPoint.getPointX();
        BigDecimal tempH = BigDecimal.valueOf(thumbLeftMax)
                .subtract(BigDecimal.valueOf(thumbLeftMin));
        float halfThumbX = tempH
                .multiply(BigDecimal.valueOf(0.5f))
                .add(BigDecimal.valueOf(thumbLeftMin)).floatValue();
        if (curThumbX < halfThumbX) {
            drawIconLeft(canvas);
        } else {
            drawIconRight(canvas);
        }
    }

    private void drawIconLeft(Canvas canvas) {
        if (leftHolder != null) {
            canvas.drawPixelMapHolderRect(leftHolder, iconLeftRF, iconPaint);
        } else {
            if (leftVector != null) {
                leftVector.setBounds(
                        (int) iconLeftRF.left, (int) iconLeftRF.top,
                        (int) iconLeftRF.right, (int) iconLeftRF.bottom);
                leftVector.drawToCanvas(canvas);
            }
        }
        if (rightHolderInactive != null) {
            canvas.drawPixelMapHolderRect(rightHolderInactive, iconRightRF, iconPaint);
        } else if (rightVectorInactive != null) {
            rightVectorInactive.setBounds(
                    (int) iconRightRF.left, (int) iconRightRF.top,
                    (int) iconRightRF.right, (int) iconRightRF.bottom);
            rightVectorInactive.drawToCanvas(canvas);
        } else if (rightHolder != null) {
            canvas.drawPixelMapHolderRect(rightHolder, iconRightRF, iconPaint);
        } else {
            if (rightVector != null) {
                rightVector.setBounds(
                        (int) iconRightRF.left, (int) iconRightRF.top,
                        (int) iconRightRF.right, (int) iconRightRF.bottom);
                rightVector.drawToCanvas(canvas);
            }
        }
    }

    private void drawIconRight(Canvas canvas) {
        if (leftHolderInactive != null) {
            canvas.drawPixelMapHolderRect(leftHolderInactive, iconLeftRF, iconPaint);
        } else if (leftVectorInactive != null) {
            leftVectorInactive.setBounds(
                    (int) iconLeftRF.left, (int) iconLeftRF.top,
                    (int) iconLeftRF.right, (int) iconLeftRF.bottom);
            leftVectorInactive.drawToCanvas(canvas);
        } else if (leftHolder != null) {
            canvas.drawPixelMapHolderRect(leftHolder, iconLeftRF, iconPaint);
        } else {
            if (leftVector != null) {
                leftVector.setBounds(
                        (int) iconLeftRF.left, (int) iconLeftRF.top,
                        (int) iconLeftRF.right, (int) iconLeftRF.bottom);
                leftVector.drawToCanvas(canvas);
            }
        }
        if (rightHolder != null) {
            canvas.drawPixelMapHolderRect(rightHolder, iconRightRF, iconPaint);
        } else {
            if (rightVector != null) {
                rightVector.setBounds(
                        (int) iconRightRF.left, (int) iconRightRF.top,
                        (int) iconRightRF.right, (int) iconRightRF.bottom);
                rightVector.drawToCanvas(canvas);
            }
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (moving) {
            return false;
        }
        int action = touchEvent.getAction();
        float touchCurrentX;
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                touchLastX = getRawX(touchEvent);
                break;
            case TouchEvent.POINT_MOVE:
                touchCurrentX = getRawX(touchEvent);
                float diffX = BigDecimal.valueOf(touchCurrentX)
                        .subtract(BigDecimal.valueOf(touchLastX)).floatValue();
                float curThumbLeft = BigDecimal.valueOf(thumbPoint.getPointX())
                        .add(BigDecimal.valueOf(diffX)).floatValue();
                if (curThumbLeft > thumbLeftMin && curThumbLeft < thumbLeftMax) {
                    curThumbLeft = Math.max(curThumbLeft, thumbLeftMin);
                    curThumbLeft = Math.min(curThumbLeft, thumbLeftMax);
                    thumbPoint.modify(curThumbLeft, thumbPoint.getPointY());
                    thumbColorChange();
                    invalidate();
                }
                touchLastX = touchCurrentX;
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                moveToDst();
                break;
        }
        return true;
    }

    private void moveToDst() {
        float curThumbX = thumbPoint.getPointX();
        float distance;
        if (currentChecked == Checked.LEFT) {
            distance = BigDecimal.valueOf(thumbLeftMax)
                    .subtract(BigDecimal.valueOf(curThumbX)).floatValue();
        } else {
            distance = BigDecimal.valueOf(curThumbX)
                    .subtract(BigDecimal.valueOf(thumbLeftMin)).floatValue();
        }
        BigDecimal tempD = BigDecimal.valueOf(thumbLeftMax)
                .subtract(BigDecimal.valueOf(thumbLeftMin));
        long duration = BigDecimal.valueOf(distance)
                .divide(tempD, BigDecimal.ROUND_HALF_UP)
                .multiply(BigDecimal.valueOf(300f)).longValue();
        AnimatorValue toDstAnim = new AnimatorValue();
        toDstAnim.setDuration(duration);
        toDstAnim.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        toDstAnim.setValueUpdateListener((animatorValue, v) -> {
            float temp = currentChecked == Checked.LEFT ?
                    curThumbX + distance * v :
                    curThumbX - distance * v;
            temp = Math.max(temp, thumbLeftMin);
            temp = Math.min(temp, thumbLeftMax);
            thumbPoint.modify(temp, thumbPoint.getPointY());
            thumbColorChange();
            invalidate();
        });
        toDstAnim.setStateChangedListener(stateChangedListener);
        toDstAnim.start();
    }

    private final Animator.StateChangedListener stateChangedListener = new Animator.StateChangedListener() {
        @Override
        public void onStart(Animator animator) {
            moving = true;
        }

        @Override
        public void onStop(Animator animator) {
            moving = false;
        }

        @Override
        public void onCancel(Animator animator) {
            moving = false;
        }

        @Override
        public void onEnd(Animator animator) {
            end();
        }

        @Override
        public void onPause(Animator animator) {
        }

        @Override
        public void onResume(Animator animator) {
        }
    };

    private void end() {
        if (currentChecked == Checked.LEFT) {
            currentChecked = Checked.RIGHT;
        } else {
            currentChecked = Checked.LEFT;
        }
        if (listener != null) {
            listener.onCheckChanged(currentChecked);
        }
        moving = false;
    }

    private float getRawX(TouchEvent touchEvent) {
        return touchEvent.getPointerScreenPosition(0).getX();
    }

    private void thumbColorChange() {
        float curThumbX = thumbPoint.getPointX();
        BigDecimal tempH = BigDecimal.valueOf(thumbLeftMax)
                .subtract(BigDecimal.valueOf(thumbLeftMin));
        float halfThumbX = tempH.multiply(BigDecimal.valueOf(0.5f))
                .add(BigDecimal.valueOf(thumbLeftMin)).floatValue();
        thumbPaint.setColor(new Color(curThumbX < halfThumbX ?
                thumbColorLeft : thumbColorRight));
        float iconScale = 0.5f;
        float changeSize = BigDecimal.valueOf(halfThumbX)
                .subtract(BigDecimal.valueOf(thumbLeftMin)).floatValue();
        if (curThumbX < halfThumbX) {
            BigDecimal t1 = BigDecimal.valueOf(curThumbX)
                    .subtract(BigDecimal.valueOf(thumbLeftMin));
            BigDecimal t2 = t1
                    .divide(BigDecimal.valueOf(changeSize), BigDecimal.ROUND_HALF_UP)
                    .multiply(BigDecimal.valueOf(0.5f));
            iconScale = BigDecimal.valueOf(1).subtract(t2).floatValue();
        } else {
            if (curThumbX > halfThumbX) {
                BigDecimal t3 = BigDecimal.valueOf(curThumbX)
                        .subtract(BigDecimal.valueOf(halfThumbX));
                BigDecimal t4 = t3
                        .divide(BigDecimal.valueOf(changeSize), BigDecimal.ROUND_HALF_UP)
                        .multiply(BigDecimal.valueOf(0.5f));
                iconScale = BigDecimal.valueOf(0.5f).add(t4).floatValue();
            }
        }
        thumbColorChange1(iconScale);
    }

    private void thumbColorChange1(float iconScale) {
        BigDecimal t5 = BigDecimal.valueOf(iconBottom)
                .subtract(BigDecimal.valueOf(iconTop));
        float centerH = t5
                .multiply(BigDecimal.valueOf(0.5f))
                .add(BigDecimal.valueOf(iconTop)).floatValue();
        float scale = iconSize * 0.5f * iconScale;
        float tempT = BigDecimal.valueOf(centerH)
                .subtract(BigDecimal.valueOf(scale)).floatValue();
        float tempB = BigDecimal.valueOf(centerH)
                .add(BigDecimal.valueOf(scale)).floatValue();
        iconLeftRF.modify(
                BigDecimal.valueOf(thumbLeftMin)
                        .subtract(BigDecimal.valueOf(scale)).floatValue(),
                tempT,
                BigDecimal.valueOf(thumbLeftMin)
                        .add(BigDecimal.valueOf(scale)).floatValue(),
                tempB);
        iconRightRF.modify(
                BigDecimal.valueOf(thumbLeftMax)
                        .subtract(BigDecimal.valueOf(scale)).floatValue(),
                tempT,
                BigDecimal.valueOf(thumbLeftMax)
                        .add(BigDecimal.valueOf(scale)).floatValue(),
                tempB);
    }

    /**
     * 获取比较之后的测量大小
     * EstimateSpec.NOT_EXCEED:     -->最大模式--AT_MOST
     * EstimateSpec.PRECISE:        -->精确模式--EXACTLY
     * EstimateSpec.UNCONSTRAINT:   -->未指定模式--UNSPECIFIED
     *
     * @param estimateConfig 测量配置
     * @param fallbackSize   默认值
     * @return 大小
     */
    private int getSize(int estimateConfig, int fallbackSize) {
        int mode = EstimateSpec.getMode(estimateConfig);
        int size = EstimateSpec.getSize(estimateConfig);
        if (mode == EstimateSpec.NOT_EXCEED) {
            return Math.min(size, fallbackSize);
        } else if (mode == EstimateSpec.UNCONSTRAINT) {
            return fallbackSize;
        } else {
            return size;
        }
    }

    /**
     * 获取IconSwitch小部件当前Check的状态
     *
     * @return checked状态
     */
    public Checked getChecked() {
        return currentChecked;
    }

    /**
     * 设置监听器
     *
     * @param listener 监听器
     */
    public void setCheckedChangeListener(CheckedChangeListener listener) {
        this.listener = listener;
    }

    /**
     * Switch切换改变监听器
     */
    public interface CheckedChangeListener {
        void onCheckChanged(Checked current);
    }

    /**
     * Switch作用切换枚举
     */
    public enum Checked {
        LEFT {
            @Override
            public Checked toggle() {
                return RIGHT;
            }
        },
        RIGHT {
            @Override
            public Checked toggle() {
                return LEFT;
            }
        };

        /**
         * toggle
         *
         * @return checked
         */
        public abstract Checked toggle();
    }

}